﻿///<reference path="../typings/jquery/jquery.d.ts"/>
///<reference path="../typings/linq/linq.3.0.3-Beta4.d.ts"/>
///<reference path="../typings/knockout/knockout.d.ts"/>
///<reference path="Promotion.ts"/>

module Enferno.Public.Shopping {

    export class Basket {
        public id: string;
        public items: KnockoutObservableArray<BasketItem>;
        public freights: KnockoutObservableArray<BasketItem>;
        public payments: KnockoutObservableArray<BasketItem>;
        public promotions: KnockoutObservableArray<Promotion>;
        public customerId: number;
        public companyId: number;
        public isOpen: KnockoutObservable<boolean>;
        public onHandMessage: string;
        public isEditable: KnockoutObservable<boolean>;
        public isInitialized: KnockoutObservable<boolean>;

        public getQuantityToAdd: () => number;
        public priceFormator: (price: number) => string;
        public static onInitializedFunctions = new Array<(initializedBasket: Basket) => void>();

        public onItemAdded: (itemAdded: BasketItem) => void;
        public onPromotionApplied: (promotionApplicationResult: PromotionApplicationResult) => void;

        public static activeBasket: Basket = null;
        public static domNodeBindingSelector = ".shopping-basket";

        public registerDiscountPromotions: KnockoutObservableArray<Promotion>;

        public static init() {
            //fetch the basket (or create a new on if none exists)
            Basket.getActiveBasket().then(basket => {
                //set up bindings
                $(document).on("click", ".add-to-basket", basket.onAddToBasketClicked);
                $(document).on("click", ".toggle-basket", basket.onToggleBasketClicked);
            });
        }

        public static getActiveBasket(): JQueryPromise<Basket> {
            var deferred = $.Deferred<Basket>();

            if (Basket.activeBasket != null) {
                Basket.bindActiveBasket();
                deferred.resolve(Basket.activeBasket);
            } else {
                $.get(UrlHelper.getUrl(Services.GetBasket))
                    .then(fetchedBasket => {
                        var basket = new Basket(fetchedBasket);
                        Basket.activeBasket = basket;

                        Enumerable.from(Basket.onInitializedFunctions).reverse().forEach(fn => fn(basket));
                        Basket.bindActiveBasket();

                        basket.isInitialized(true);

                        deferred.resolve(basket);
                    });
            }

            return deferred.promise();
        }

        private static bindActiveBasket() {
            var domNodes = $(Basket.domNodeBindingSelector);
            Enumerable.from(domNodes).forEach(domNode => ko.applyBindings(Basket.activeBasket, domNode));

        }

        constructor(serverItem?: any) {
            this.items = ko.observableArray<BasketItem>();
            this.freights = ko.observableArray<BasketItem>();
            this.payments = ko.observableArray<BasketItem>();
            this.promotions = ko.observableArray<Promotion>();
            this.registerDiscountPromotions = ko.observableArray<Promotion>();

            this.promotions.subscribe((newValue: Array<Promotion>) => {
                this.updateRegisterDiscountPromotionsFromPromotions();
            });

            this.isOpen = ko.observable<boolean>(false);
            this.isEditable = ko.observable<boolean>(true);
            this.isInitialized = ko.observable<boolean>(false);
            if (serverItem != null) {
                this.setDataFromServerBasket(serverItem);
            }
        }

        public getFormattedPriceString(price: number): string {
            return this.priceFormator ? this.priceFormator(price) : price.toString();
        }

        public disableEditability(): void {
            this.isEditable(false);
        }

        public enableEditability(): void {
            this.isEditable(true);
        }

        public onApplyDiscountClicked(discountCode: string): void {
            this.applyDiscount(discountCode);
        }

        public onRemovePromotionClicked(promotionId: number, resetDiscountCode: boolean): void {
            this.removePromotion(promotionId, resetDiscountCode);
        }

        private updateRegisterDiscountPromotionsFromPromotions() {
            var discountPromotions = Enumerable.from(this.promotions()).where(promotion => promotion.requirementSeed == "10" || promotion.requirementSeed == "22").toArray();
            this.registerDiscountPromotions.removeAll();
            Enumerable.from(discountPromotions).forEach(discountPromotion => this.registerDiscountPromotions.push(discountPromotion));
        }

        private itemQuantityOnChange = (newQuantity: number, basketItem: BasketItem): void => {
            //update on server
            var basket = this;
            basket.updateItem(basketItem);
        }

        private onAddToBasketClicked = (eventObject: JQueryEventObject, args: any[]): any => {
            var basket = this;

            var partNo = $(eventObject.target).data("partno");
            var priceListId = $(eventObject.target).data("pricelistid");

            var quantity: number;

            if (basket.getQuantityToAdd != null) {
                quantity = basket.getQuantityToAdd();
            } else {
                quantity = $(eventObject.target).data("quantity") | 1;
            }

            var basketItem = BasketItem.createItem(partNo, priceListId, quantity);

            basket.addItem(basketItem);
            basket.isOpen(true);
        }

        private onToggleBasketClicked = (eventObject: JQueryEventObject, args: any[]): any => {
            var basket = this;
            if (basket.isOpen()) {
                basket.close();
            } else {
                basket.open();
            }
        }

        private setDataFromServerBasket(serverBasket: any): void {
            this.id = serverBasket.id;
            this.customerId = serverBasket.customerId;
            this.companyId = serverBasket.companyId;
            this.onHandMessage = serverBasket.onHandMessage;
            this.items.removeAll();
            this.payments.removeAll();
            this.freights.removeAll();
            this.promotions.removeAll();


            if (serverBasket.items != null) {
                Enumerable.from(serverBasket.items).forEach(serverItem => {
                    this.items.push(new BasketItem(serverItem, this.itemQuantityOnChange));
                });
                this.items.sort((item1, item2) => { return item1.lineNo == item2.lineNo ? 0 : (item1.lineNo < item2.lineNo ? -1 : 1); });
            }

            if (serverBasket.payments != null) {
                Enumerable.from(serverBasket.payments).forEach(serverPayment => {
                    this.payments.push(new BasketItem(serverPayment, this.itemQuantityOnChange));
                });
                this.payments.sort((item1, item2) => { return item1.lineNo == item2.lineNo ? 0 : (item1.lineNo < item2.lineNo ? -1 : 1); });
            }

            if (serverBasket.freights != null) {
                Enumerable.from(serverBasket.freights).forEach(serverFreight => {
                    this.freights.push(new BasketItem(serverFreight, this.itemQuantityOnChange));
                });
                this.freights.sort((item1, item2) => { return item1.lineNo == item2.lineNo ? 0 : (item1.lineNo < item2.lineNo ? -1 : 1); });
            }

            if (serverBasket.promotions != null) {
                Enumerable.from(serverBasket.promotions).forEach(serverPromotion => {
                    this.promotions.push(new Promotion(serverPromotion));
                });
            }
        }

        public open() {
            this.isOpen(true);
        }

        public close() {
            this.isOpen(false);
        }

        public getTotalSum(): number {
            var sum = Enumerable.from(this.items()).sum(item => {
                return item.getPriceSum();
            });

            sum += Enumerable.from(this.freights()).sum(freight => {
                return freight.getPriceSum();
            });

            sum += Enumerable.from(this.payments()).sum(payment => {
                return payment.getPriceSum();
            });
            return sum;

        }

        public getTotalSumFormattedString(): string {
            var totalSum = this.getTotalSum();
            if (this.priceFormator != null) {
                return this.priceFormator(totalSum);
            }
            return totalSum.toString();
        }

        public addItem(item: BasketItem) {
            var url = UrlHelper.getUrl(Services.AddItem, [this.id.toString()]);
            $.post(url, item).then(updatedBasket => {
                this.setDataFromServerBasket(updatedBasket);
                if (this.onItemAdded != null) {
                    this.onItemAdded(item);
                }
            });
        }

        public deleteItem = (basketItem: BasketItem) => {
            var basket = this;
            var url = UrlHelper.getUrl(Services.DeleteItem, [basket.id.toString()]);
            $.post(url, { lineNo: basketItem.lineNo }).then(updatedBasket => {
                this.setDataFromServerBasket(updatedBasket);
            });
        }

        public updateItem(item: BasketItem) {
            var url = UrlHelper.getUrl(Services.UpdateItem, [this.id.toString()]);
            $.post(url, item).then(updatedBasket => {
                this.setDataFromServerBasket(updatedBasket);
            });
        }

        public applyDiscount(discountCode: string) {
            var url = UrlHelper.getUrl(Services.ApplyDiscount, [this.id.toString()]);
            $.post(url, { discountCode: discountCode }).then(applyPromotionResult => {
                var result = new PromotionApplicationResult(applyPromotionResult);
                this.setDataFromServerBasket(applyPromotionResult.basket);

                if (this.onPromotionApplied != null)
                    this.onPromotionApplied(result);
            });
        }

        public removePromotion(promotionId: number, resetDiscountCode: boolean) {
            var url = UrlHelper.getUrl(Services.RemovePromotion, [this.id.toString()]);
            $.post(url, { promotionId: promotionId, resetDiscountCode: resetDiscountCode }).then(updatedBasket => {
                this.setDataFromServerBasket(updatedBasket);
            });
        }
    }

    export class BasketItem {

        constructor(serverItem?: any, quantityOnChange?: (newQuantity: number, basketItem: BasketItem) => void) {
            if (serverItem != null) {
                this.id = serverItem.id;
                this.productUrl = ko.observable<string>(serverItem.productUrl);
                this.imageUrl = ko.observable<string>(serverItem.imageUrl);
                this.quantity = ko.observable<number>(serverItem.quantity);
                this.quantityDelayed = ko.computed<number>(this.quantity);
                this.quantityDelayed.extend({ throttle: 500 });

                this.displayPrice = ko.observable<number>(serverItem.displayPrice);
                this.originalPrice = serverItem.originalPrice;
                this.vatRate = ko.observable<number>(serverItem.vatRate);
                this.name = ko.observable<string>(serverItem.name);

                this.subHeader = ko.observable<string>(serverItem.subHeader);
                this.lineNo = serverItem.lineNo;
                this.priceListId = serverItem.priceListId;
                this.partNo = ko.observable<string>(serverItem.partNo);
                this.isEditable = serverItem.isEditable;
                this.isDiscountable = serverItem.isDiscountable;

                this.quantityDelayed.subscribe(newValue => quantityOnChange(newValue, this));

                this.unitOfMeasurement = serverItem.unitOfMeasurement;
            }
        }

        public static createItem(partNo: string, priceListId: number, quantity: number = 1): BasketItem {
            var item = new BasketItem();
            item.partNo = ko.observable<string>(partNo);
            item.priceListId = priceListId;
            item.quantity = ko.observable<number>(quantity);

            return item;
        }

        public getPriceSum(): number {
            if (this.displayPrice !== undefined && this.quantity !== undefined)
                return this.displayPrice() * this.quantity();

            return 0;
        }

        public id: number;
        public productUrl: KnockoutObservable<string>;
        public imageUrl: KnockoutObservable<string>;
        public quantity: KnockoutObservable<number>;
        public quantityDelayed: KnockoutComputed<number>;

        public displayPrice: KnockoutObservable<number>;
        public originalPrice: number;
        public vatRate: KnockoutObservable<number>;
        public name: KnockoutObservable<string>;
        public subHeader: KnockoutObservable<string>;
        public lineNo: number;
        public partNo: KnockoutObservable<string>;
        public priceListId: number;
        public isEditable: boolean;
        public isDiscountable: boolean;
        public unitOfMeasurement: string;


    }

    export class Services {
        public static AddItem = "api/shoppingbasket/{0}/addItem";
        public static UpdateItem = "api/shoppingbasket/{0}/updateItem";
        public static DeleteItem = "api/shoppingbasket/{0}/deleteItem";
        public static ApplyDiscount = "api/shoppingbasket/{0}/applyDiscount";
        public static RemovePromotion = "api/shoppingbasket/{0}/removePromotion";
        public static GetBasket = "api/shoppingbasket/get";
    }

    export class UrlHelper {

        public static baseUrl = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '') + '/';

        public static getUrl(path: string, inserts?: string[]): string {
            var url = UrlHelper.baseUrl + path;
            if (inserts != null)
                url = StringHelper.Format(url, inserts);

            return url;
        }

    }

    export class StringHelper {
        public static Format(str: string, args: any[]): string {
            if (args != null) {
                for (var i = 0; i < args.length; i++) {
                    str = str.replace(
                        RegExp("\\{" + i + "\\}", 'g'), args[i].toString());
                }
            }
            return str;
        }
    }

} 